﻿/*
<File>
	<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License see="//License.txt"/>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2007/11/19 20:00</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
	<History>
		<Revision Date="2008-06-25" ContributorName="Ladislav Burkovsky" ContributorEmail="burkovsky@freenet.de">
			Population of Log4Net properties dictionary.</Revision>
	</History>
</File>
*/

using System;
using System.Runtime.CompilerServices;

using log4net;
using log4net.Core;

namespace DanielVaughan.Logging.LogStrategies
{
	/// <summary>
	/// The <see cref="ILogStrategy"/> for Log4Net.
	/// </summary>
	class Log4NetStrategy : ILogStrategy
	{
		static readonly ILog defaultLog = LogManager.GetLogger(typeof(Log4NetStrategy));

		/// <summary>
		/// Logs the specified client log entry.
		/// <seealso cref="IServerLogEntry"/>
		/// </summary>
		/// <param name="logEntry">The log entry.</param>
		public void Write(IClientLogEntry logEntry)
		{
			ILog log = defaultLog;
			if (logEntry.LogName != null)
			{
				log = LogManager.GetLogger(logEntry.LogName);
			}

			/* Create a Log4Net event data instance, 
			 * and populate it with our log entry information. */
			LoggingEventData data = new LoggingEventData();
			if (logEntry.ExceptionMemento != null)
			{	/* Use the exception memento to write 
				 * the message and stack trace etc. */
				data.ExceptionString = logEntry.ExceptionMemento.ToString();
			}
			
			data.Level = GetLog4NetLevel(logEntry.LogLevel);
			ICodeLocation location = logEntry.CodeLocation;
			if (location != null)
			{
				data.LocationInfo = new LocationInfo(location.ClassName, location.MethodName, 
					location.FileName, location.LineNumber.ToString());
			}

			data.LoggerName = logEntry.LogName;
			data.Message = string.Format("{0}\nMachineName:{1}", logEntry.Message, logEntry.MachineName);
			data.ThreadName = logEntry.ThreadName;
			data.TimeStamp = logEntry.OccuredAt;
			data.UserName = logEntry.UserName;

			/* Copy custom properties into log4net Properties. */
			if (logEntry.Properties != null && logEntry.Properties.Count > 0)
			{
				var properties = new log4net.Util.PropertiesDictionary();
				foreach (var prop in logEntry.Properties)
				{
					properties[prop.Key] = prop.Value;
				}
				data.Properties = properties;
			}

			var loggingEvent = new LoggingEvent(data);
			WriteThreadSafe(log.Logger, loggingEvent);
		}

		/// <summary>
		/// Logs the specified server log entry.
		/// <seealso cref="IServerLogEntry"/>
		/// </summary>
		/// <param name="logEntry">The log entry.</param>
		public void Write(IServerLogEntry logEntry)
		{
			ILog log = defaultLog;
			if (logEntry.LogName != null)
			{
				log = LogManager.GetLogger(logEntry.LogName);
			}

			LoggingEventData data = new LoggingEventData();
			data.Domain = logEntry.AppDomain;
			Exception exception = logEntry.Exception;
			if (exception != null)
			{
				data.ExceptionString = string.Format("{0}{1}{2}", 
					exception, Environment.NewLine, exception.StackTrace);
			}
			data.Identity = logEntry.Identity;
			data.Level = GetLog4NetLevel(logEntry.LogLevel);
			ICodeLocation location = logEntry.CodeLocation;
			if (location != null)
			{
				data.LocationInfo = new LocationInfo(location.ClassName, location.MethodName,
					location.FileName, location.LineNumber.ToString());
			}
			
			data.LoggerName = logEntry.LogName;
			data.Message = logEntry.Message;
			data.ThreadName = logEntry.ThreadName;
			data.TimeStamp = logEntry.OccuredAt;
			data.UserName = logEntry.UserName;

			/* Copy custom properties into log4net Properties. */
			if (logEntry.Properties != null && logEntry.Properties.Count > 0)
			{
				var properties = new log4net.Util.PropertiesDictionary();
				foreach (var prop in logEntry.Properties)
				{
					properties[prop.Key] = prop.Value;
				}
				data.Properties = properties;
			}

			var loggingEvent = new LoggingEvent(data);
			WriteThreadSafe(log.Logger, loggingEvent);
		}

		/// <summary> 
		/// All Write log calls are done asynchronously because the DanielVaughan.Logging.Log 
		/// uses the AppPool to dispatch calls to this method. Therefore we need to ensure 
		/// that the call to Log is threadsafe. That is, if an appender such as FileAppender is used, 
		/// then we need to ensure it is called from no more than one thread at a time. 
		/// </summary>
		/// <param name="logger">The logger.</param>
		/// <param name="loggingEvent">The logging event.</param>
		[MethodImpl(MethodImplOptions.Synchronized)]
		static void WriteThreadSafe(ILogger logger, LoggingEvent loggingEvent)
		{
			logger.Log(loggingEvent);
		}

		/// <summary>
		/// Gets the log level for the log with the specified name.
		/// </summary>
		/// <param name="clientInfo">Caller information.</param>
		/// <returns>The threshold log level.</returns>
		public LogLevel GetLogLevel(IClientInfo clientInfo)
		{
			if (clientInfo == null)
			{
				return LogLevel;
			}

			ILog log = LogManager.GetLogger(clientInfo.LogName);
			return GetLogLevel(log);
		}

		#region Default log level
		LogLevel? lowestLogLevel;
		readonly object lowestLogLevelLock = new object();

		LogLevel LogLevel
		{
			get
			{
				if (!lowestLogLevel.HasValue)
				{
					lock (lowestLogLevelLock)
					{
						if (!lowestLogLevel.HasValue)
						{
							var loggers = LogManager.GetCurrentLoggers();
							lowestLogLevel = LogLevel.None;
							foreach (var log in loggers)
							{
								var logLevel = GetLogLevel(log);
								if (logLevel < lowestLogLevel)
								{
									lowestLogLevel = logLevel;
								}
							}
						}
					}
				}

				return lowestLogLevel.Value;
			}
		}
		#endregion

		/// <summary>
		/// Gets equivalent Clog log level of the log4net log.
		/// </summary>
		/// <param name="log">The log.</param>
		/// <returns></returns>
		static LogLevel GetLogLevel(ILog log)
		{
			if (log.IsDebugEnabled)
			{
				return LogLevel.Debug;
			}
			if (log.IsInfoEnabled)
			{
				return LogLevel.Info;
			}
			if (log.IsWarnEnabled)
			{
				return LogLevel.Warn;
			}
			if (log.IsErrorEnabled)
			{
				return LogLevel.Error;
			}
			if (log.IsFatalEnabled)
			{
				return LogLevel.Fatal;
			}
			return LogLevel.None;
		}

		/// <summary>
		/// Converts a Clog LogLevel to the log4net level equivalent.
		/// </summary>
		/// <param name="level">The level to convert.</param>
		/// <returns>The log4net level.</returns>
		static Level GetLog4NetLevel(LogLevel level)
		{
			switch (level)
			{
				case LogLevel.Debug:
					return Level.Debug;
				case LogLevel.Info:
					return Level.Info;
				case LogLevel.Warn:
					return Level.Warn;
				case LogLevel.Error:
					return Level.Error;
				case LogLevel.Fatal:
					return Level.Fatal;
				default:
					return Level.Off;
			}
		}
	}
}
